package pl.edu.mimuw.irs.core.model.analysis.dd;

import java.io.Serializable;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import pl.edu.mimuw.irs.core.model.analysis.Time;
import pl.edu.mimuw.irs.core.model.analysis.dd.TimeStrategy.Type;
import pl.edu.mimuw.irs.core.model.analysis.resources.RequirementPlace;
import pl.edu.mimuw.irs.core.model.analysis.resources.ResourcePlace;
import pl.edu.mimuw.irs.core.model.design.net.IRSActivity;
import pl.edu.mimuw.irs.core.model.design.net.IRSNode;
import pl.edu.mimuw.irs.core.util.Converter;

public class ActivityWrapper implements Serializable {

	private static final long serialVersionUID = 2559104163987209869L;
	
	// *** Attributes
	
	private IRSActivity activity;
	/* 
	 * This is the leaf of the parent tree. We can find the direct above activity
	 * by traversing it upwards.
	 */
	private PathNode ascendingPath;
	/*
	 * What resources are currently involved in this activity.
	 */
	private Map<ResourcePlace, Set<RequirementPlace>> involvedResources;
	/* 
	 * This is the root of the children tree. We can find the direct below activities
	 * by traversing it downwards.
	 */
	private PathNode descendingPath;
	private boolean ancestryCreated;
	private boolean started;
	private boolean registered;
	private TimeStrategy timeStrategy;
	private Time rootTimestamp;
	private Time timestamp;
	/*
	 * Cached value of isDeadlocking() result.
	 */
	private Boolean cachedValue;
	
	public IRSActivity getActivity() {
		return activity;
	}
	public Map<ResourcePlace, Set<RequirementPlace>> getInvolvedResources() {
		if (involvedResources == null) involvedResources = new HashMap<ResourcePlace, Set<RequirementPlace>>();
		return involvedResources;
	}
	public Set<ResourcePlace> getDerivedResources() {
		Set<ResourcePlace> derived = new HashSet<ResourcePlace>();
		derived.addAll(getInvolvedResources().keySet());
		if (getAscendingPath() != null) derived.addAll(getAscendingPath().getDerivedResources());
		return derived;
	}
	public void setAscendingPath(PathNode ascendingPath) {
		this.ascendingPath = ascendingPath;
	}
	public PathNode getAscendingPath() {
		return ascendingPath;
	}
	public PathNode getDescendingPath() {
		return descendingPath;
	}
	public boolean isStarted() {
		return started;
	}
	public void setStarted(boolean started) {
		this.started = started;
		/* 
		 * When "started" state is changed, we want to inform on which path it 
		 * happened.
		 */
		if (getAscendingPath() != null) getAscendingPath().setStarted(started);
		/*
		 * Mark when the job begins/ends.
		 */
		if (started) {
			resetTimeStrategy();
			if (getAscendingPath() != null) {
				this.timestamp = getAscendingPath().getRoot().getOwner().getTimestamp().copy();
			} else {
				this.timestamp = getRootTimestamp().copy();
			}
		} else {
			Time timestamp;
			if (getAscendingPath() != null) {
				timestamp = getAscendingPath().getRoot().getOwner().getTimestamp();
			} else {
				timestamp = getRootTimestamp();
			}
			//this.timestamp.add(getActivity().getTimeUnits());
			this.timestamp.add(getTimeStrategy().getTimeTaken());
			timestamp.setMax(getTimestamp());
		}
	}
	public boolean isRegistered() {
		return registered;
	}
	public TimeStrategy getTimeStrategy() {
		if (timeStrategy == null) timeStrategy = new TimeStrategy(this, Type.PESIMISTIC);
		return timeStrategy;
	}
	public void setTimeStrategy(TimeStrategy timeStrategy) {
		this.timeStrategy = timeStrategy;
	}
	public Time getRootTimestamp() {
		return rootTimestamp;
	}
	public void setRootTimestamp(Time rootTimestamp) {
		this.rootTimestamp = rootTimestamp;
	}
	public Time getTimestamp() {
		if (timestamp == null) timestamp = new Time();
		return timestamp;
	}
	public Boolean getCachedValue() {
		return cachedValue;
	}
	public void clearCache() {
		cachedValue = null;
		PathNode pathNode = getAscendingPath();
		if (pathNode != null) pathNode.clearCache();
	}

	// *** Constructors
	
	public ActivityWrapper(IRSActivity activity) {
		this.activity = activity;
	}
	
	// *** Methods
	
	public void reset() {
		this.timestamp = new Time();
		resetTimeStrategy();
	}

	public void resetTimeStrategy() {
		this.timeStrategy = null;
	}
	
	public void createDescendingPath(Map<IRSActivity, ActivityWrapper> wrappers) {
		/* Create ancestry tree only for the first time. */
		if (!ancestryCreated) {
			IRSActivity activity = getActivity();
			PathNode path = new PathNode(activity.getFragmentation());
			createDescendingPath(activity, path, wrappers);
			//this.descendingPath = simplifyDescendingPath(path); XXX: nie ustawia sie owner
			this.descendingPath = path;
			this.descendingPath.setOwner(this);
			ancestryCreated = true;
		}
		/* Else the ancestry tree creation has already been performed. */
	}
	
	private void createDescendingPath(IRSNode parentNode, PathNode parentPath,
			Map<IRSActivity, ActivityWrapper> wrappers) {

		switch (parentNode.getFragmentation()) {
		case ALTERNATIVE:
		case SEQUENCE:
		case CONCURRENCY:
		case ITERATION:
			for (IRSNode node : parentNode.getChildren()) {
				PathNode path = new PathNode(parentPath, node.getFragmentation());
				createDescendingPath(node, path, wrappers);
			}
			break;
		case ACTIVITY:
			/* 
			 * If we find an activity, then stop building path - pass it 
			 * to new activity.
			 */
			for (IRSNode node : parentNode.getChildren()) {
				if (node instanceof IRSActivity) {
					IRSActivity activity = Converter.asIRSActivity(node);
					ActivityWrapper wrapper = wrappers.get(activity);
					PathNode activityNode = 
						new PathNode(parentPath, activity.getFragmentation(), 
								wrapper);
					wrapper.createDescendingPath(wrappers);
					wrapper.setAscendingPath(activityNode);
					wrapper.setRootTimestamp(getRootTimestamp());
				}
			}
			break;
		default:
			/*
			 * If fragmentation is NONE we stop building path.
			 */
			break;
		}
		
	}
	
//	private PathNode simplifyDescendingPath(PathNode descendingPath) {
//		/*
//		 * Remove dead ends (paths which do not end on Activity). For
//		 * later usage the NullActivityWrapper nodes will be made for
//		 * dead ending subtree - to distinguish situations like 
//		 * ALTERNATIVE[Path(Activity)] and ALTERNATIVE[Path(Activity), 
//		 * DeadEnd].
//		 * This is because ALTERNATIVE[Path(Activity), DeadEnd] does not 
//		 * require anything to be finished, where ALTERNATIVE[Path(Activity)]
//		 * does.
//		 */
//		List<PathNode> newChildren;
//		switch (descendingPath.getFragmentation().getType()) {
//		case ALTERNATIVE:
//			/*
//			 * In alternative we want to know if there exist at least one
//			 * dead end. If yes - we want to mark it by putting a dead end
//			 * between the children, so we would know, that this node can end
//			 * without any further requirements.
//			 */
//			PathNode deadEnd = null;
//			newChildren = new LinkedList<PathNode>();
//			for (PathNode child : descendingPath.getChildren()) {
//				PathNode simplePath = simplifyDescendingPath(child);
//				if (deadEnd == null && simplePath.isDeadEnd()) {
//					deadEnd = simplePath;
//				} else if (!simplePath.isDeadEnd()) {
//					newChildren.add(simplePath);
//				}
//			}
//			if (newChildren.size() == 0) {
//				if (deadEnd == null) {
//					/*
//					 * Impossible situation.
//					 */
//					throw new InternalException(Cause.WORKFLOW_FRAGMENTATION_NO_CHILDREN, 
//							descendingPath.getFragmentation().toString());
//				} else {
//					/*
//					 * Only dead ends in this node, so whole node is a dead end.
//					 */
//					return new DeadEnd();
//				}
//			} else {
//				if (deadEnd == null) {
//					/* 
//					 * Create node with new children.
//					 */
//					return new PathNode(descendingPath.getFragmentation(),
//							newChildren);
//				} else {
//					/* 
//					 * Add dead end to the children, so we can distinguish
//					 * situations mentioned in the beginning of this method.
//					 */
//					newChildren.add(deadEnd);
//					return new PathNode(descendingPath.getFragmentation(),
//							newChildren);
//				}
//			}
//		case SEQUENCE:
//		case CONCURRENCY:
//			/*
//			 * Here we don't care about dead ends - if exists, we just 
//			 * omit them.
//			 */
//			newChildren = new LinkedList<PathNode>();
//			for (PathNode child : descendingPath.getChildren()) {
//				PathNode simplePath = simplifyDescendingPath(child);
//				if (!simplePath.isDeadEnd()) {
//					newChildren.add(simplePath);
//				}
//			}
//			if (newChildren.size() == 0) {
//				/*
//				 * All children are dead ends. So this node is a dead end
//				 * as well.
//				 */
//				return new DeadEnd();
//			} else {
//				/* 
//				 * Create node with new children.
//				 */
//				return new PathNode(descendingPath.getFragmentation(),
//						newChildren);
//			}
//		case ACTIVITY:
//			/* 
//			 * We do not need to store information about activity
//			 * fragmentation. This is already known in path because its
//			 * activity is not null.
//			 */
//			for (PathNode child : descendingPath.getChildren()) {
//				if (child.getActivity() != null) {
//					return child;
//				}
//			}
//		default:
//			// TOD : ITERATION...
//			return new DeadEnd();
//		}
//	}

	@Override
	public String toString() {
		return "*" + activity.toString() + "*";
	}

	public boolean isAncestorOf(IRSActivity irsActivity) {
		return getActivity().isAncestorOf(irsActivity);
	}

	public boolean isAncestorOf(ActivityWrapper activity) {
		return isAncestorOf(activity.getActivity());
	}
	
	
	public boolean isDerived(ResourcePlace resource) {
		if (getInvolvedResources().containsKey(resource)) return true;
		else {
			PathNode pathNode = getAscendingPath();
			if (pathNode == null) return false;
			else return pathNode.getRoot().getOwner().isDerived(resource);
		}
	}

}
